home *** CD-ROM | disk | FTP | other *** search
/ Internet Info 1994 March / Internet Info CD-ROM (Walnut Creek) (March 1994).iso / networking / ip / ka9q / aztecnos.arc / FTPSERV.C < prev    next >
Encoding:
C/C++ Source or Header  |  1989-03-19  |  15.1 KB  |  651 lines

  1. /* FTP Server state machine - see RFC 959 */
  2. #include <stdio.h>
  3. #include <ctype.h>
  4. #include <time.h>
  5. #include "global.h"
  6. #include "mbuf.h"
  7. #include "socket.h"
  8. #include "ftp.h"
  9. #include "ftpserv.h"
  10. #include "proc.h"
  11.  
  12. void ftpserv();
  13. char *pathname();
  14. static int pport(),ftplogin(),sendit(),recvit();
  15. extern char Hostname[],Version[];
  16.  
  17. /* Command table */
  18. static char *commands[] = {
  19.     "user",
  20. #define    USER_CMD    0
  21.     "acct",
  22. #define    ACCT_CMD    1
  23.     "pass",
  24. #define    PASS_CMD    2
  25.     "type",
  26. #define    TYPE_CMD    3
  27.     "list",
  28. #define    LIST_CMD    4
  29.     "cwd",
  30. #define    CWD_CMD        5
  31.     "dele",
  32. #define    DELE_CMD    6
  33.     "name",
  34. #define    NAME_CMD    7
  35.     "quit",
  36. #define    QUIT_CMD    8
  37.     "retr",
  38. #define    RETR_CMD    9
  39.     "stor",
  40. #define    STOR_CMD    10
  41.     "port",
  42. #define    PORT_CMD    11
  43.     "nlst",
  44. #define    NLST_CMD    12
  45.     "pwd",
  46. #define    PWD_CMD        13
  47.     "xpwd",            /* For compatibility with 4.2BSD */
  48. #define    XPWD_CMD    14
  49.     "mkd ",
  50. #define    MKD_CMD        15
  51.     "xmkd",            /* For compatibility with 4.2BSD */
  52. #define    XMKD_CMD    16
  53.     "xrmd",            /* For compatibility with 4.2BSD */
  54. #define    XRMD_CMD    17
  55.     "rmd ",
  56. #define    RMD_CMD        18
  57.     "stru",
  58. #define    STRU_CMD    19
  59.     "mode",
  60. #define    MODE_CMD    20
  61.     NULLCHAR
  62. };
  63.  
  64. /* Response messages */
  65. static char banner[] = "220 %s FTP version %s ready at %s\r\n";
  66. static char badcmd[] = "500 Unknown command\r\n";
  67. static char unsupp[] = "500 Unsupported command or option\r\n";
  68. static char givepass[] = "331 Enter PASS command\r\n";
  69. static char logged[] = "230 Logged in\r\n";
  70. static char typeok[] = "200 Type OK\r\n";
  71. static char only8[] = "501 Only logical bytesize 8 supported\r\n";
  72. static char deleok[] = "250 File deleted\r\n";
  73. static char mkdok[] = "200 MKD ok\r\n";
  74. static char delefail[] = "550 Delete failed\r\n";
  75. static char pwdmsg[] = "257 \"%s\" is current directory\r\n";
  76. static char badtype[] = "501 Unknown type \"%s\"\r\n";
  77. static char badport[] = "501 Bad port syntax\r\n";
  78. static char unimp[] = "502 Command not yet implemented\r\n";
  79. static char bye[] = "221 Goodbye!\r\n";
  80. static char nodir[] = "553 Can't read directory \"%s\"\r\n";
  81. static char cantopen[] = "550 Can't read file \"%s\"\r\n";
  82. static char sending[] = "150 Opening data connection for %s %s\r\n";
  83. static char cantmake[] = "553 Can't create \"%s\"\r\n";
  84. static char writerr[] = "552 Write error\r\n";
  85. static char portok[] = "200 Port command okay\r\n";
  86. static char rxok[] = "226 File received OK\r\n";
  87. static char txok[] = "226 File sent OK\r\n";
  88. static char noperm[] = "550 Permission denied\r\n";
  89. static char noconn[] = "425 Data connection reset\r\n";
  90. static char notlog[] = "530 Please log in with USER and PASS\r\n";
  91. static char okay[] = "200 Ok\r\n";
  92.  
  93. static int Sftp = -1;    /* Prototype socket for service */
  94.  
  95. /* Start up FTP service */
  96. ftpstart(argc,argv)
  97. int argc;
  98. char *argv[];
  99. {
  100.     struct sockaddr_in lsocket;
  101.     int s;
  102.  
  103.     psignal(Curproc,0);    /* Don't keep the parser waiting */
  104.     chname(Curproc,"FTP listener");
  105.  
  106.     lsocket.sin_family = AF_INET;
  107.     lsocket.sin_addr.s_addr = Ip_addr;
  108.     if(argc < 2)
  109.         lsocket.sin_port = IPPORT_FTP;
  110.     else
  111.         lsocket.sin_port = atoi(argv[1]);
  112.  
  113.     Sftp = socket(AF_INET,SOCK_STREAM,0);
  114.     bind(Sftp,(char *)&lsocket,sizeof(lsocket));
  115.     listen(Sftp,1);
  116.     for(;;){
  117.         if((s = accept(Sftp,NULLCHAR,(int *)NULL)) == -1)
  118.             break;    /* Service is shutting down */
  119.  
  120.         /* Spawn a server */
  121.         newproc("ftpserv",2048,ftpserv,s,NULL);
  122.     }
  123. }
  124. void
  125. ftpserv(s,unused)
  126. int s;    /* Socket with user connection */
  127. void *unused;
  128. {
  129.     struct ftpserv ftp;
  130.     char **cmdp,buf[512],*arg,*cp,*cp1,*file,*mode;
  131.     long t;
  132.     int cnt,i;
  133.     struct sockaddr_in socket;
  134. #ifndef    CPM
  135.     FILE *dir();
  136. #endif
  137.  
  138.     memset((char *)&ftp,0,sizeof(ftp));    /* Start with clear slate */
  139.     ftp.data = -1;
  140.  
  141.     sockowner(s,Curproc);        /* We own it now */
  142.     ftp.control = s;
  143.     /* Set default data port */
  144.     i = SOCKSIZE;
  145.     getpeername(s,(char *)&socket,&i);
  146.     socket.sin_port = IPPORT_FTPD;
  147.     ASSIGN(ftp.port,socket);
  148.  
  149.     log(s,"open FTP");
  150.     time(&t);
  151.     cp = ctime(&t);
  152.     if((cp1 = strchr(cp,'\n')) != NULLCHAR)
  153.         *cp1 = '\0';
  154.     usprintf(s,banner,Hostname,Version,cp);
  155. loop:    if((cnt = recvline(s,buf,sizeof(buf))) == -1){
  156.         /* He closed on us */
  157.         goto finish;
  158.     }
  159.     if(cnt == 0){
  160.         /* Can't be a legal FTP command */
  161.         usprintf(ftp.control,badcmd);
  162.         goto loop;
  163.     }    
  164.     rip(buf);
  165. #ifdef    UNIX
  166.     /* Translate first word to lower case */
  167.     for(cp = buf;*cp != ' ' && *cp != '\0';cp++)
  168.         *cp = tolower(*cp);
  169. #else
  170.     /* Translate entire buffer to lower case */
  171.     for(cp = buf;*cp != '\0';cp++)
  172.         *cp = tolower(*cp);
  173. #endif
  174.     /* Find command in table; if not present, return syntax error */
  175.     for(cmdp = commands;*cmdp != NULLCHAR;cmdp++)
  176.         if(strncmp(*cmdp,buf,strlen(*cmdp)) == 0)
  177.             break;
  178.     if(*cmdp == NULLCHAR){
  179.         usprintf(ftp.control,badcmd);
  180.         goto loop;
  181.     }
  182.     /* Allow only USER, PASS and QUIT before logging in */
  183.     if(ftp.cd == NULLCHAR || ftp.path == NULLCHAR){
  184.         switch(cmdp-commands){
  185.         case USER_CMD:
  186.         case PASS_CMD:
  187.         case QUIT_CMD:
  188.             break;
  189.         default:
  190.             usprintf(ftp.control,notlog);
  191.             goto loop;
  192.         }
  193.     }
  194.     arg = &buf[strlen(*cmdp)];
  195.     while(*arg == ' ')
  196.         arg++;
  197.  
  198.     /* Execute specific command */
  199.     switch(cmdp-commands){
  200.     case USER_CMD:
  201.         if(ftp.username != NULLCHAR)
  202.             free(ftp.username);
  203.         if((ftp.username = strdup(arg)) == NULLCHAR){
  204.             shutdown(ftp.control,1);
  205.             goto finish;
  206.         }
  207.         usprintf(ftp.control,givepass);
  208.         break;
  209.     case TYPE_CMD:
  210.         switch(arg[0]){
  211.         case 'A':
  212.         case 'a':    /* Ascii */
  213.             ftp.type = ASCII_TYPE;
  214.             usprintf(ftp.control,typeok);
  215.             break;
  216.         case 'l':
  217.         case 'L':
  218.             while(*arg != ' ' && *arg != '\0')
  219.                 arg++;
  220.             if(*arg == '\0' || *++arg != '8'){
  221.                 usprintf(ftp.control,only8);
  222.                 break;
  223.             }
  224.             ftp.type = LOGICAL_TYPE;
  225.             ftp.logbsize = 8;
  226.             break;
  227.         case 'B':
  228.         case 'b':    /* Binary */
  229.         case 'I':
  230.         case 'i':    /* Image */
  231.             ftp.type = IMAGE_TYPE;
  232.             usprintf(ftp.control,typeok);
  233.             break;
  234.         default:    /* Invalid */
  235.             usprintf(ftp.control,badtype,arg);
  236.             break;
  237.         }
  238.         break;
  239.     case QUIT_CMD:
  240.         usprintf(ftp.control,bye);
  241.         goto finish;
  242.     case RETR_CMD:
  243.         file = pathname(ftp.cd,arg);
  244.         switch(ftp.type){
  245.         case IMAGE_TYPE:
  246.         case LOGICAL_TYPE:
  247.             mode = Binmode[READ_BINARY];
  248.             break;
  249.         case ASCII_TYPE:
  250.             mode = "r";
  251.             break;
  252.         }
  253.         if(!permcheck(&ftp,RETR_CMD,file)){
  254.              usprintf(ftp.control,noperm);
  255.         } else if((ftp.fp = fopen(file,mode)) == NULLFILE){
  256.             usprintf(ftp.control,cantopen,file);
  257.         } else {
  258.             log(ftp.control,"RETR %s",file);
  259.             sendit(&ftp,"RETR",file);
  260.         }
  261.         free(file);
  262.         break;
  263.     case STOR_CMD:
  264.         file = pathname(ftp.cd,arg);
  265.         switch(ftp.type){
  266.         case IMAGE_TYPE:
  267.         case LOGICAL_TYPE:
  268.             mode = Binmode[WRITE_BINARY];
  269.             break;
  270.         case ASCII_TYPE:
  271.             mode = "w";
  272.             break;
  273.         }
  274.         if(!permcheck(&ftp,STOR_CMD,file)){
  275.              usprintf(ftp.control,noperm);
  276.             free(file);
  277.              break;
  278.         } else if((ftp.fp = fopen(file,mode)) == NULLFILE){
  279.             usprintf(ftp.control,cantmake,file);
  280.         } else {
  281.             log(ftp.control,"STOR %s",file);
  282.             recvit(&ftp,"STOR",file);
  283.         }
  284.         free(file);
  285.         break;
  286.     case PORT_CMD:
  287.         if(pport(&ftp.port,arg) == -1){
  288.             usprintf(ftp.control,badport);
  289.         } else {
  290.             usprintf(ftp.control,portok);
  291.         }
  292.         break;
  293. #ifndef CPM
  294.     case LIST_CMD:
  295.         file = pathname(ftp.cd,arg);
  296.         if(!permcheck(&ftp,RETR_CMD,file)){
  297.              usprintf(ftp.control,noperm);
  298.         } else if((ftp.fp = dir(file,1)) == NULLFILE){
  299.             usprintf(ftp.control,nodir,file);
  300.         } else {
  301.             sendit(&ftp,"LIST",file);
  302.         }
  303.         free(file);
  304.         break;
  305.     case NLST_CMD:
  306.         file = pathname(ftp.cd,arg);
  307.         if(!permcheck(&ftp,RETR_CMD,file)){
  308.              usprintf(ftp.control,noperm);
  309.         } else if((ftp.fp = dir(file,0)) == NULLFILE){
  310.             usprintf(ftp.control,nodir,file);
  311.         } else {
  312.             sendit(&ftp,"NLST",file);
  313.         }
  314.         free(file);
  315.         break;
  316.     case CWD_CMD:
  317.         file = pathname(ftp.cd,arg);
  318.         if(!permcheck(&ftp,RETR_CMD,file)){
  319.              usprintf(ftp.control,noperm);
  320.             free(file);
  321. #ifdef    MSDOS
  322.         /* Don'tcha just LOVE %%$#@!! MS-DOS? */
  323.         } else if(strcmp(file,"\\") == 0 || access(file,0) == 0){
  324. #else
  325.         } else if(access(file,0) == 0){    /* See if it exists */
  326. #endif
  327.             /* Succeeded, record in control block */
  328.             free(ftp.cd);
  329.             ftp.cd = file;
  330.             usprintf(ftp.control,pwdmsg,file);
  331.         } else {
  332.             /* Failed, don't change anything */
  333.             usprintf(ftp.control,nodir,file);
  334.             free(file);
  335.         }
  336.         break;
  337.     case XPWD_CMD:
  338.     case PWD_CMD:
  339.         usprintf(ftp.control,pwdmsg,ftp.cd);
  340.         break;
  341. #else
  342.     case LIST_CMD:
  343.     case NLST_CMD:
  344.     case CWD_CMD:
  345.     case XPWD_CMD:
  346.     case PWD_CMD:
  347. #endif
  348.     case ACCT_CMD:        
  349.         usprintf(ftp.control,unimp);
  350.         break;
  351.     case DELE_CMD:
  352.         file = pathname(ftp.cd,arg);
  353.         if(!permcheck(&ftp,DELE_CMD,file)){
  354.              usprintf(ftp.control,noperm);
  355.         } else if(unlink(file) == 0){
  356.             usprintf(ftp.control,deleok);
  357.         } else {
  358.             usprintf(ftp.control,delefail);
  359.         }
  360.         free(file);
  361.         break;
  362.     case PASS_CMD:
  363.         ftplogin(&ftp,arg);            
  364.         break;
  365. #ifndef    CPM
  366.     case XMKD_CMD:
  367.     case MKD_CMD:
  368.         file = pathname(ftp.cd,arg);
  369.         if(!permcheck(&ftp,MKD_CMD,file)){
  370.             usprintf(ftp.control,noperm);
  371.         } else if(mkdir(file,0777) == 0){
  372.             usprintf(ftp.control,mkdok);
  373.         } else {
  374.             usprintf(ftp.control,cantmake);
  375.         }
  376.         free(file);
  377.         break;
  378.     case XRMD_CMD:
  379.     case RMD_CMD:
  380.         file = pathname(ftp.cd,arg);
  381.         if(!permcheck(&ftp,RMD_CMD,file)){
  382.              usprintf(ftp.control,noperm);
  383.         } else if(rmdir(file) == 0){
  384.             usprintf(ftp.control,deleok);
  385.         } else {
  386.             usprintf(ftp.control,delefail);
  387.         }
  388.         free(file);
  389.         break;
  390.     case STRU_CMD:
  391.         if(tolower(arg[0]) != 'f')
  392.             usprintf(ftp.control,unsupp);
  393.         else
  394.             usprintf(ftp.control,okay);
  395.         break;
  396.     case MODE_CMD:
  397.         if(tolower(arg[0]) != 's')
  398.             usprintf(ftp.control,unsupp);
  399.         else
  400.             usprintf(ftp.control,okay);
  401.         break;
  402.     }
  403. #endif
  404.     goto loop;
  405. finish:
  406.     log(ftp.control,"close FTP");
  407.     /* Clean up */
  408.     close_s(ftp.control);
  409.     if(ftp.data != -1)
  410.         close_s(ftp.data);
  411.     if(ftp.fp != NULLFILE)
  412.         fclose(ftp.fp);
  413.     if(ftp.username != NULLCHAR)
  414.         free(ftp.username);
  415.     if(ftp.path != NULLCHAR)
  416.         free(ftp.path);
  417.     if(ftp.cd != NULLCHAR)
  418.         free(ftp.cd);
  419. }
  420.  
  421. /* Shut down FTP server */
  422. ftp0()
  423. {
  424.     if(Sftp != -1)
  425.         close_s(Sftp);
  426.     Sftp = -1;
  427. }
  428. static
  429. int
  430. pport(sock,arg)
  431. struct sockaddr_in *sock;
  432. char *arg;
  433. {
  434.     int32 n;
  435.     int i;
  436.  
  437.     n = 0;
  438.     for(i=0;i<4;i++){
  439.         n = atoi(arg) + (n << 8);
  440.         if((arg = strchr(arg,',')) == NULLCHAR)
  441.             return -1;
  442.         arg++;
  443.     }
  444.     sock->sin_addr.s_addr = n;
  445.     n = atoi(arg);
  446.     if((arg = strchr(arg,',')) == NULLCHAR)
  447.         return -1;
  448.     arg++;
  449.     n = atoi(arg) + (n << 8);
  450.     sock->sin_port = n;
  451.     return 0;
  452. }
  453. /* Attempt to log in the user whose name is in ftp->username and password
  454.  * in pass
  455.  */
  456. static
  457. ftplogin(ftp,pass)
  458. struct ftpserv *ftp;
  459. char *pass;
  460. {
  461.     char buf[80],*cp,*cp1;
  462.     FILE *fp;
  463.     int anony = 0;
  464.  
  465.     if((fp = fopen(Userfile,"r")) == NULLFILE){
  466.         /* Userfile doesn't exist */
  467.         usprintf(ftp->control,noperm);
  468.         return;
  469.     }
  470.     while(fgets(buf,sizeof(buf),fp),!feof(fp)){
  471.         if(buf[0] == '#')
  472.             continue;    /* Comment */
  473.         if((cp = strchr(buf,' ')) == NULLCHAR)
  474.             /* Bogus entry */
  475.             continue;
  476.         *cp++ = '\0';        /* Now points to password */
  477.         if(strcmp(ftp->username,buf) == 0)
  478.             break;        /* Found user name */
  479.     }
  480.     if(feof(fp)){
  481.         /* User name not found in file */
  482.         fclose(fp);
  483.         usprintf(ftp->control,noperm);
  484.         return;
  485.     }
  486.     fclose(fp);
  487.     /* Look for space after password field in file */
  488.     if((cp1 = strchr(cp,' ')) == NULLCHAR){
  489.         /* Invalid file entry */
  490.         usprintf(ftp->control,noperm);
  491.         return;
  492.     }
  493.     *cp1++ = '\0';    /* Now points to path field */
  494.     if(strcmp(cp,"*") == 0)
  495.         anony = 1;    /* User ID is password-free */
  496.     if(!anony && strcmp(cp,pass) != 0){
  497.         /* Password required, but wrong one given */
  498.         usprintf(ftp->control,noperm);
  499.         return;
  500.     }
  501.     if((cp = strchr(cp1,' ')) == NULLCHAR){
  502.         /* Permission field missing */
  503.         usprintf(ftp->control,noperm);
  504.         return;
  505.     }
  506.     *cp++ = '\0';    /* now points to permission field */
  507.  
  508.     /* Set up current directory and path prefix */
  509.     ftp->cd = strdup(cp1);
  510.     ftp->path = strdup(cp1);
  511.     
  512.     /* And finally set the permission bits */
  513.     ftp->perms = atoi(cp);
  514.  
  515.     usprintf(ftp->control,logged);
  516.     if(!anony)
  517.         log(ftp->control,"%s logged in",ftp->username);
  518.     else
  519.         log(ftp->control,"%s logged in, ID %s",ftp->username,pass);
  520. }        
  521.  
  522. #ifdef    MSDOS
  523. /* Illegal characters in a DOS filename */
  524. char badchars[] = "\"[]:|<>+=;,";
  525. #endif
  526.  
  527. /* Return 1 if the file operation is allowed, 0 otherwise */
  528. permcheck(ftp,op,file)
  529. struct ftpserv *ftp;
  530. int op;
  531. char *file;
  532. {
  533.     char *cp;
  534.  
  535.     if(file == NULLCHAR || ftp->path == NULLCHAR)
  536.         return 0;    /* Probably hasn't logged in yet */
  537. #ifdef    MSDOS
  538.     /* Check for characters illegal in MS-DOS file names */
  539.     for(cp = badchars;*cp != '\0';cp++){
  540.         if(strchr(file,*cp) != NULLCHAR)
  541.             return 0;    
  542.     }
  543. #endif
  544. #if    (defined(AMIGA) || defined(MAC))
  545. #else
  546.     /* The target file must be under the user's allowed search path */
  547.     if(strncmp(file,ftp->path,strlen(ftp->path)) != 0)
  548.         return 0;
  549. #endif
  550.  
  551.     switch(op){
  552.     case RETR_CMD:
  553.         /* User must have permission to read files */
  554.         if(ftp->perms & FTP_READ)
  555.             return 1;
  556.         return 0;
  557.     case DELE_CMD:
  558.     case RMD_CMD:
  559.         /* User must have permission to (over)write files */
  560.         if(ftp->perms & FTP_WRITE)
  561.             return 1;
  562.         return 0;
  563.     case STOR_CMD:
  564.     case MKD_CMD:
  565.         /* User must have permission to (over)write files, or permission
  566.          * to create them if the file doesn't already exist
  567.          */
  568.         if(ftp->perms & FTP_WRITE)
  569.             return 1;
  570.         if(access(file,2) == -1 && (ftp->perms & FTP_CREATE))
  571.             return 1;
  572.         return 0;
  573.     }
  574.     return 0;    /* "can't happen" -- keep lint happy */
  575. }
  576. static 
  577. sendit(ftp,command,file)
  578. struct ftpserv *ftp;
  579. char *command;
  580. char *file;
  581. {
  582.     long total;
  583.     struct sockaddr_in dport;
  584.  
  585.     ftp->data = socket(AF_INET,SOCK_STREAM,0);
  586.     dport.sin_family = AF_INET;
  587.     dport.sin_addr.s_addr = Ip_addr;
  588.     dport.sin_port = IPPORT_FTPD;
  589.     bind(ftp->data,(char *)&dport,SOCKSIZE);
  590.     usprintf(ftp->control,sending,command,file);
  591.     if(connect(ftp->data,(char *)&ftp->port,SOCKSIZE) == -1){
  592.         fclose(ftp->fp);
  593.         usprintf(ftp->control,noconn);
  594.         close_s(ftp->data);
  595.         ftp->data = -1;
  596.         return;
  597.     }
  598.     /* Do the actual transfer */
  599.     total = sendfile(ftp->fp,ftp->data,ftp->type);
  600.  
  601.     if(total == -1){
  602.         /* An error occurred on the data connection */
  603.         usprintf(ftp->control,noconn);
  604.         shutdown(ftp->data,2);    /* Blow away data connection */
  605.     } else {
  606.         usprintf(ftp->control,txok);
  607.         close_s(ftp->data);
  608.     }
  609.     fclose(ftp->fp);
  610.     ftp->data = -1;
  611. }
  612. static
  613. recvit(ftp,command,file)
  614. struct ftpserv *ftp;
  615. char *command;
  616. char *file;
  617. {
  618.     struct sockaddr_in dport;
  619.     long total;
  620.  
  621.     ftp->data = socket(AF_INET,SOCK_STREAM,0);
  622.     dport.sin_family = AF_INET;
  623.     dport.sin_addr.s_addr = Ip_addr;
  624.     dport.sin_port = IPPORT_FTPD;
  625.     bind(ftp->data,(char *)&dport,SOCKSIZE);
  626.     usprintf(ftp->control,sending,command,file);
  627.     if(connect(ftp->data,(char *)&ftp->port,SOCKSIZE) == -1){
  628.         fclose(ftp->fp);
  629.         usprintf(ftp->control,noconn);
  630.         close_s(ftp->data);
  631.         ftp->data = -1;
  632.         return;
  633.     }
  634.     total = recvfile(ftp->fp,ftp->data,ftp->type);
  635.  
  636. #ifdef    CPM
  637.     if(ftp->type == ASCII_TYPE)
  638.         fputc(CTLZ,ftp->fp);
  639. #endif
  640.     if(total == -1) {
  641.         /* An error occurred while writing the file */
  642.         usprintf(ftp->control,writerr);
  643.         shutdown(ftp->data,2);    /* Blow it away */
  644.     } else {
  645.         usprintf(ftp->control,rxok);
  646.         close_s(ftp->data);
  647.     }
  648.     ftp->data = -1;
  649.     fclose(ftp->fp);
  650. }
  651.